{A Static Site Builder for Windows} v0.8
Published in Software. Updated 2015/06/24. Today I'm writing about the tool that I use to build this site, which fills the role of a static CMS. It lets me write in the friendly Markdown notation to create articles quickly, throw the applicable images in a directory and run a script to rebuild the site content. Like other static CMS systems, this tool (yet to be properly named) uses text based content and templates which are combined to produce a (hopefully) pretty site. Everything is organised into directories. Everything starts with a Paths.txt file, that looks a lot like the one below: This file is expected to be in the same directory as the site builder executable, and specifies where all of the data is or will go. The content directory contains a collection of text files with the .md file extension, expected to be in an enhanced Markdown notation. Each file should also have a preamble that borrows a subset of YAML, namely associative arrays. An article may look like this: The markdown processing is done by Jeff Atwood's extremely useful MarkdownSharp library, which is apparently a descendent of Milan Negovan's Markdown .NET. Macros are files that live in the directory specified by macrosDir, and can be referred to by articles or templates to add common or variable content.
Articles (and other content) may contain references to Markdown macros, the syntax of which I borrowed from Aaron Parecki's post Some Enhancements to Markdown. The syntax to use a macro is For example if one wanted to include a reference to a YouTube video, there's a macro to do that, and it would be included in the article like so: This would create a YouTube embed pointing to the desired URL. There are two main types of macro available: This category contains both plaintext (.txt) and Markdown (.md) macros. Both work in the same way, but the Markdown macro passes its output through MarkdownSharp before returning the result. The principle method these macros use is string replacement. For example the YouTube embed macro is simply: Where %p#% stands for a parameter, and %v#% stands for a (the) value. Macros may also incorporate any data from the article properties (including those inherited from the template). If the article had a "Title" attribute, the %Title% would work too. In all cases the named value is replaced with the real value - simple. If the macro refers to something that doesn't exist, then it is not replaced.
So text macros are really simple to set up, but they are limited in that they only get access to the current article's information. XSLT macros are the opposite. XSLT macros are more complicated to write but get access to the metadata for every article. This makes them useful for features such as site navigation. The file extension for a XSLT macro is .xsl, and the file is expected to be a standard XML transform using XPath 1.0 expressions. This is built on .NET's included XSL engine.
XSLT macros work by running a transformation over a dynamically constructed XML document, which at its simplest looks like the example below: It is possible to group and sort articles. Another text file with the same name as the macro's XSL file, but with a .groups extension, may be included. If found, this will cause the XML to be constructed differently. For example, a .groups file containing: would cause the following XML to be generated for the macro: Any number of levels of grouping may be specified, in which case groups are nested inside of each other. If there is a need to combine features of both macro types, it is possible for a macro's output to include a macro invocation in the same format as that used for an article. Where an article includes a Template attribute, the tool will try to find a template with the same name in the templates directory. It is not mandatory for an article to use a template, but it makes life much easier. At this stage, only Markdown templates (.mdt) are supported. A template may look like this: The %content% marker is used to determine where the article content should be placed after processing. It is a moot point at present, but the article content does not have use the same processing engine as the template. Properties specified in the template are made available to macros running in the associated article, and vice-versa. Virtual articles are a way to generate pages based on the metadata of all loaded articles. They are based on XSL as well, and are expected to generate a namespace-less XML document containing an Articles tag, which in turns contains Article tags, containing Content tags. Within each Content tag there is expected to be a Markdown article in the same format as a regular article. The file extension for a virtual article generator stylesheet is .xslmd . An example article generator stylesheet to produce category article lists is: This stylesheet has an associated .groups file, containing just one entry: The input XML for this stylesheet would look like this: For which the output would be: The article generator then extracts every Article node and processes its content through the Markdown article processor. The actual content generation is left to a macro named categoryPageThumbnails which has an associated groups file. The tool is able to resize images in a given directory for display on different devices. That means high-resolution photos can be just be dropped in the site content and the site builder can take care of ensuring that appropriately sized images are available for different devices. Image processing is driven by another two other configuration files that are expected to be in the same directory as the executable, named ImagePaths.txt and ImageSizes.txt. This file is quite simple, is simply indicates where the input and output directories are: This file indicates which sizes the each image in the processing directory should be sized to. Normally each file is output to various different sizes, with a different extension. The above file requests the following:
- Any file larger than 1920x1080 should be scaled to fit and saved on the original file name. For JPEGs, the default save quality is 95%.
- And file larger than 800x600 should be scaled to fit and saved with "-sml" added to the end of the file name before the extension. If JPEG save at 75% quality.
- Any file larger than 300x240 will be scaled and then cropped to exactly fit 300x240, and saved with "-tmb" added before the file extension. IF JPEG, save at 75% quality. At this stage the following file extensions are supported: Images are always saved in their original format. Current limitations (to be improved in future) are: For the moment, the tool is available from here. Soon I'm planning to put the source up on GitHub for others to use and improve as they see fit. Static websites have their advantages, fer particular types of content. A static site can be deployed virtually anywhere (even on ultra-basic hosting services), and can be served extremely fast because there is no server-side processing to do. For a site that is largely a one-way dialogue like this one, it works well. Although Jekyll and similar tools are a good fit in theory, they generally assumed the user was on a Mac, with Ruby, and thus were super-easy to set up on that platform. They could be made to work on Windows, but I wanted a one-step solution that could work out of the box on Windows and didn't depend on any particular web stack being present. For three reasons: Because I wrote it to suit my needs, and my needs don't yet cover X. I did design it to be fairly flexible and extensible though, so once the code is online feel free to add X.How it works
Paths file
#This file specifies paths to the site content and output directories
articlesDir=C:\Website\Content\
macrosDir=C:\Website\Macros\
templatesDir=C:\Website\Templates\
virtualArticlesDir=C:\Website\Virtual Content\
outputDir=C:\WebsiteOutput\
Content
---
Date:2015-05-05
Category:Software
Title:My awesome project
Template:Post
---
Today I wrote an awesome app that does...
Macros
![:macroName parameter1 parameter2](value)
. The only mandatory element is the macro name. If a parameter would need to contain spaces, these can be substituted for underscores. If a parameter would need to contain underscores, these can be substituted for double-underscores.![:youtube 1366 768](https://www.youtube.com/embed/M1ufW2INWmM)
Text macros
<iframe width="%p1%" height="%p2%" src="%v1%" frameborder="0"></iframe>
XSLT Macros
<Articles>
<Article>
<!--Every attribute of the article is listed in an XML node here-->
<Title>My article</Title>
<Date>2015-02-05</Date>
</Article>
<Article>
<Title>My follow-up article</Title>
<Date>2015-02-10</Date>
</Article>
<Articles>
Date Descending
<Articles>
<Group Attribute="Date" Value="2015-02-05">
<Article>
<Title>My article</Title>
<Date>2015-02-05</Date>
</Article>
</Group>
<Group Attribute="Date" Value="2015-02-10">
<Article>
<Title>My follow-up article</Title>
<Date>2015-02-10</Date>
</Article>
</Group>
<Articles>
Templates
---
Tags:BlogPost
---
<!DOCTYPE html>
<html>
<head>
<title>![:articleTitle]</title>
</head>
<body>
<div id="divMain" class="main">
%content%
</div>
</body>
</html>
Virtual Articles
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl"
>
<xsl:output method="xml" indent="no"/>
<!--Note: Indentation of article content is important here. Any indentation preceding the article content (after the preamble) will cause the Markdown
engine to treat HTML markup as code (and in turn escape it for display on the web) rather than as HTML.-->
<xsl:template match="/">
<xsl:element name="Articles">
<xsl:for-each select="//Group[@Value != '']">
<xsl:element name="Article">
<xsl:element name="Content">
---
Title:Posts in <xsl:value-of select="@Value"/>
Template:categoryPage
Path:Category-<xsl:value-of select="@Value"/>.html
---
\![:categoryPageThumbnails <xsl:value-of select="translate(@Value, ' ', '_')"/>]
</xsl:element>
</xsl:element>
</xsl:for-each>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
Category
<?xml version="1.0" encoding="utf-8"?>
<Articles>
<Group Attribute="Category" Value="">
<Article>
<Title>Eddie's Site</Title>
<Category />
<Tags />
<Date />
<DateModified />
<HeadingImage />
<Template>index</Template>
<Path />
</Article>
</Group>
<Group Attribute="Category" Value="Electronics">
<Article>
<Title>Inside the ASUS Taichi Power Plug</Title>
<Category>Electronics</Category>
<Tags>
<Value>Electronics</Value>
<Value> Computing</Value>
</Tags>
<HeadingImage>img/TaichiHeader</HeadingImage>
<Template>Post</Template>
<Path />
</Article>
<Article>
<Title>Xbox Controller Repair</Title>
<Category>Electronics</Category>
<Tags>
<Value>Gaming</Value>
<Value>Electronics</Value>
</Tags>
<HeadingImage>img/XbCR1</HeadingImage>
<Template>Post</Template>
<Path />
</Article>
</Group>
<Group Attribute="Category" Value="Site News">
<Article>
<Title>Goodbye Old Site</Title>
<Category>Site News</Category>
<Tags>
<Value>Site News</Value>
</Tags>
<HeadingImage>img/OldEddiesoftSite-banner</HeadingImage>
<Template>Post</Template>
<Path />
</Article>
</Group>
</Articles>
<Articles><Article><Content>
---
Title:Posts in Electronics
Template:categoryPage
Path:Category-Electronics.html
---
![:categoryPageThumbnails Electronics]
</Content></Article><Article><Content>
---
Title:Posts in Site News
Template:categoryPage
Path:Category-Site News.html
---
![:categoryPageThumbnails Site_News]
</Content></Article></Articles>
Image Processing
ImagePaths.txt
#Format is [source|destination]=<path>
source=C:\Website\Content\img\
destination=C:\WebsiteOutput\img\
ImageSizes.txt
#Format is <width>x<height> <suffix>
1920x1080
800x600 -sml 75%
300x240:crop -tmb 75%
Processing Sequence
Limitations
Download
Why
...a static site?
...build a new tool?
...isn't there an editor built in?
... doesn't it do X?